﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Dynamic;
using System.Xml.Linq;
using System.Xml;
using System.IO;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using AttributeData = Roslyn.Compilers.CSharp.AttributeData;

using King.Reflection;
using King.Extensions;
using King.Extensions.Collections;

namespace King.CSharp.Reflection {

    public static partial class SymbolInfoExtensions {

        internal static string GetCSharpName(this TypeName typeName, out int arity) {
            var name = typeName.Name;

            arity = 0;

            // parse for arity
            var match = Regex.Match(name, @"^(?<name>[^`]*)`(?<arity>\d*)$");
            if (match.Success) {
                name = match.Get("name");
                arity = match.Get<int>("arity");
            }

            return name;
        }

        public static IEnumerable<dynamic> SyntheticValues(this IEnumerable<AttributeSymbolInfo> attributes) {
            return attributes.Select(o => o.GetSyntheticValue());
        }
        public static dynamic GetSyntheticValue(this AttributeSymbolInfo attributeDataInfo) {
            return new SyntheticAttribute(attributeDataInfo);
        }

        public static TypeSymbolInfo TryGetType(this CompilationInfo compilation, Type type) {
            return compilation.TryGetType(type.AssemblyQualifiedName);
        }
        public static TypeSymbolInfo GetType(this CompilationInfo compilation, Type type) {
            return compilation.GetType(type.AssemblyQualifiedName);
        }

        public static TypeSymbolInfo TryGetType(this AssemblySymbolInfo assembly, Type type) {
            return assembly.TryGetType(type.AssemblyQualifiedName);
        }
        public static TypeSymbolInfo GetType(this AssemblySymbolInfo assembly, Type type) {
            return assembly.GetType(type.AssemblyQualifiedName);
        }

        public static IEnumerable<AttributeSymbolInfo> Attributes(this AssemblySymbolInfo symbol, Type type = null) {
            return symbol.Attributes(symbol.Compilation.GetType(type));
        }
        public static AttributeSymbolInfo GetAttribute(this AssemblySymbolInfo symbol, Type type = null) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this AssemblySymbolInfo symbol, Type type = null) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this AssemblySymbolInfo symbol, Type type) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this AssemblySymbolInfo symbol, Type type) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<AttributeSymbolInfo> Attributes(this ModuleSymbolInfo symbol, Type type = null) {
            return symbol.Attributes(symbol.Compilation.GetType(type));
        }
        public static AttributeSymbolInfo GetAttribute(this ModuleSymbolInfo symbol, Type type = null) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this ModuleSymbolInfo symbol, Type type = null) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this ModuleSymbolInfo symbol, Type type) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this ModuleSymbolInfo symbol, Type type) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<AttributeSymbolInfo> Attributes(this FieldSymbolInfo symbol, Type type = null) {
            return symbol.Attributes(symbol.Compilation.GetType(type));
        }
        public static AttributeSymbolInfo GetAttribute(this FieldSymbolInfo symbol, Type type = null) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this FieldSymbolInfo symbol, Type type = null) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this FieldSymbolInfo symbol, Type type) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this FieldSymbolInfo symbol, Type type) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<AttributeSymbolInfo> Attributes(this MethodSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.Attributes(symbol.Compilation.GetType(type), declaredOnly);
        }
        public static AttributeSymbolInfo GetAttribute(this MethodSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.GetAttributes(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this MethodSymbolInfo symbol, Type type = null, bool declaredOnly = false) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this MethodSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this MethodSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<AttributeSymbolInfo> Attributes(this ParameterSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.Attribute(symbol.Compilation.GetType(type), declaredOnly);
        }
        public static AttributeSymbolInfo GetAttribute(this ParameterSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this ParameterSymbolInfo symbol, Type type = null, bool declaredOnly = false) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this ParameterSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this ParameterSymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<AttributeSymbolInfo> Attributes(this PropertySymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.Attributes(symbol.Compilation.GetType(type), declaredOnly);
        }
        public static AttributeSymbolInfo GetAttribute(this PropertySymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.GetAttributes(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this PropertySymbolInfo symbol, Type type = null, bool declaredOnly = false) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this PropertySymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this PropertySymbolInfo symbol, Type type, bool declaredOnly = false) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        //public static IEnumerable<T> Attributes<T>(this IAttributableSymbolInfo symbol, bool declaredOnly = false) {
        //    return symbol.Attributes(typeof(T), declaredOnly).OfType<T>();
        //}
        //public static T GetAttribute<T>(this IAttributableSymbolInfo symbol, bool declaredOnly = false) {
        //    return (T)symbol.GetAttribute(typeof(T), declaredOnly);
        //}
        //public static bool IsDefined<T>(this IAttributableSymbolInfo symbol, bool declaredOnly = false) {
        //    return symbol.IsDefined(typeof(T), declaredOnly);
        //}
        
        //internal IEnumerable<dynamic> Attributes(Type type, bool declaredOnly = false) {
        //    return Attributes(Compilation.GetType(type), declaredOnly);
        //}
        //internal dynamic GetAttribute(Type type, bool declaredOnly = false) {
        //    return GetAttribute(Compilation.GetType(type), declaredOnly);
        //}
        //internal bool IsDefined(Type type, bool declaredOnly = false) {
        //    return IsDefined(Compilation.GetType(type), declaredOnly);
        //}
    }

    public abstract class SymbolInfo {

        private CompilationInfo m_compilation;
        private Symbol m_symbol;

        public SymbolInfo(CompilationInfo compilation, Symbol symbol) {
            m_compilation = compilation;
            m_symbol = symbol;
        }

        internal IEnumerable<AttributeSymbolInfo> Attribute(TypeSymbolInfo type, bool declaredOnly = false) {
            if (type != null && type.IsNamedType)
                return m_symbol.GetAttributes(type.NamedTypeSymbol).Select(o => new AttributeSymbolInfo(Compilation, this, o));

            return m_symbol.GetAttributes().Select(o => new AttributeSymbolInfo(Compilation, this, o));
        }
        internal AttributeSymbolInfo GetAttribute(TypeSymbolInfo type, bool declaredOnly = false) {
            return Attribute(type, declaredOnly).SingleOrDefault();
        }
        internal IEnumerable<dynamic> SyntheticAttributes(TypeSymbolInfo type = null, bool declaredOnly = false) {
            return Attribute(type, declaredOnly).Select(o => new SyntheticAttribute(o));
        }
        internal dynamic GetSyntheticAttribute(TypeSymbolInfo type, bool declaredOnly = false) {
            return SyntheticAttributes(type, declaredOnly).SingleOrDefault();
        }
        internal bool IsDefined(TypeSymbolInfo type, bool declaredOnly = false) {
            return Attribute(type, declaredOnly).Any();
        }

        public CompilationInfo Compilation {
            get { return m_compilation; }
        }
        public Symbol Symbol {
            get { return m_symbol; }
        }
        public abstract SymbolInfo DeclaringSymbol { get; }

        public override int GetHashCode() {
            return m_symbol.GetHashCode();
        }
        public override bool Equals(object obj) {
            if (obj == null)
                return false;
            return obj.Equals(m_symbol);
        }
    }

    public sealed class CompilationInfo {

        public static CompilationInfo Create(params string[] references) {
            return Create(references.Select(o => MetadataReferenceInfo.Create(o)).ToArray());
        }
        public static CompilationInfo Create(params MetadataReferenceInfo[] references) {
            return new CompilationInfo(references);
        }

        private static bool SymbolEquals(object lhs, object rhs) {
            var lhsPointer = lhs as PointerTypeSymbol;
            var rhsPointer = rhs as PointerTypeSymbol;

            if (lhsPointer != null && rhsPointer != null)
                return lhsPointer.PointedAtType.Equals(rhsPointer.PointedAtType);

            return object.Equals(lhs, rhs);
        }
        private static int SymbolGetHashcode(object o) {
            if (o == null)
                throw new ArgumentNullException();

            return o.GetHashCode();
        }

        private Compilation m_compilation;
        private MetadataReferenceInfo m_mscorlib;
        private IEnumerable<MetadataReferenceInfo> m_references;
        private Dictionary<string, MetadataReferenceInfo> m_pathToReferences;

        private Dictionary<Symbol, SymbolInfo> m_symbolToSymbolInfo;
        private Dictionary<MetadataReference, AssemblySymbol> m_referenceToAssembly;
        private Dictionary<AssemblyName, MetadataReferenceInfo> m_nameToReferences;
        private Dictionary<TypeSymbolInfo, string> m_keywordNames;
        private Dictionary<SyntaxNode, SyntaxTree> m_syntaxTrees;
        private Dictionary<SyntaxNode, SemanticModel> m_semanticModels;

        private CompilationInfo(CompilationInfo compilation, IEnumerable<SyntaxTree> trees) {
            m_compilation = compilation.m_compilation.AddSyntaxTrees(trees);
            ThrowOnCompilationError();

            m_references = compilation.m_references;
            m_pathToReferences = compilation.m_pathToReferences;
            m_mscorlib = compilation.m_mscorlib;

            m_syntaxTrees = trees.ToDictionary(o => o.Root);
        }
        private CompilationInfo(IEnumerable<MetadataReferenceInfo> references) {
            m_references = references;

            // associate references by paths
            m_pathToReferences = m_references.Where(o => o.File != null).ToDictionary(o => o.File);

            // bind mscorlib by file name
            if (m_mscorlib == null)
                m_mscorlib = m_pathToReferences
                    .Where(o => Path.GetFileName(o.Key).CaseInsensitveEquals("mscorlib.dll"))
                    .Select(o => o.Value).FirstOrDefault();

            // create compilation
            m_compilation = Compilation.Create("CompilationContext",
                options: new CompilationOptions(assemblyKind: AssemblyKind.DynamicallyLinkedLibrary),
                references: references.Select(o => o.MetadataReference));
            ThrowOnCompilationError();
        }

        private void ThrowOnCompilationError() {
            if (m_compilation.GetDiagnostics().Any())
            //if (m_compilation.GetDiagnostics().Any(o => o.Info.Severity == DiagnosticSeverity.Error))
                throw new ArgumentException(
                    m_compilation.GetDiagnostics().StringJoin(Environment.NewLine, o => o.ToString())
                );
        }
        private AssemblySymbol Get(MetadataReference reference) {
            if (m_referenceToAssembly == null)
                m_referenceToAssembly = new Dictionary<MetadataReference, AssemblySymbol>();

            var assembly = m_referenceToAssembly.GetValueOrDefault(reference);
            if (assembly == null)
                m_referenceToAssembly[reference] = assembly = Compilation.GetReferencedAssemblySymbol(reference);

            return assembly;
        }

        internal string GetTypeKeyword(TypeSymbolInfo type) {
            if (m_keywordNames == null) {
                m_keywordNames = new[] {
                    new { Type = typeof(object), Keyword = "object" },
                    new { Type = typeof(void), Keyword = "void" },

                    new { Type = typeof(char), Keyword = "char" },
                    new { Type = typeof(string), Keyword = "string" },

                    new { Type = typeof(sbyte), Keyword = "sbyte" },
                    new { Type = typeof(short), Keyword = "short" },
                    new { Type = typeof(int), Keyword = "int" },
                    new { Type = typeof(long), Keyword = "long" },

                    new { Type = typeof(byte), Keyword = "byte" },
                    new { Type = typeof(ushort), Keyword = "ushort" },
                    new { Type = typeof(uint), Keyword = "uint" },
                    new { Type = typeof(ulong), Keyword = "ulong" },

                    new { Type = typeof(float), Keyword = "float" },
                    new { Type = typeof(double), Keyword = "double" },
                    new { Type = typeof(decimal), Keyword = "decimal" },
                }.ToDictionary(o => this.GetType(o.Type), o => o.Keyword);
            }

            return m_keywordNames.GetValueOrDefault(type);
        }
        internal T Get<T>(Symbol symbol) {
            if (symbol == null)
                return default(T);

            if (m_symbolToSymbolInfo == null)
                m_symbolToSymbolInfo = new Dictionary<Symbol, SymbolInfo>(
                    EqualityComparer.Construct<object>(SymbolEquals, SymbolGetHashcode));

            var compilation = this;
            object result = m_symbolToSymbolInfo.GetOrCacheValue(symbol, 
                s => (SymbolInfo)typeof(T).Activate(compilation, s));

            return (T)result;
        }
        internal Compilation Compilation {
            get { return m_compilation; }
        }

        public CompilationInfo Parse(SyntaxTree tree) {
            return new CompilationInfo(this, new[] { tree });
        }
        public CompilationInfo Parse(IEnumerable<TextReader> streams) {
            var trees = streams.Select(o => SyntaxTree.ParseCompilationUnit(o.ReadToEnd()));
            return new CompilationInfo(this, trees);
        }
        public CompilationInfo Parse(TextReader stream) {
            return Parse(new[] { stream });
        }
        public CompilationInfo Parse(string code) {
            return Parse(new[] { new StringReader(code) });
        }

        public TypeSymbolInfo TryGetType(SyntaxNode node) {
            if (node == null)
                return null;

            if (m_semanticModels == null)
                m_semanticModels = new Dictionary<SyntaxNode, SemanticModel>();

            var root = node;
            while (root.Parent != null)
                root = root.Parent;

            var semanticModel = m_semanticModels.GetValueOrDefault(root);
            if (semanticModel == null) {
                var tree = m_syntaxTrees.GetValueOrDefault(root);
                if (tree == null)
                    throw new ArgumentException("Cannot resolve a SyntaxTree for the SyntaxNode.");

                semanticModel = m_semanticModels[root] = m_compilation.GetSemanticModel(tree);
            }

            var expression = node as ExpressionSyntax;
            if (expression == null)
                return null;

            var semanticInfo = semanticModel.GetSemanticInfo(expression);
            if (semanticInfo == null)
                return null;

            return Get<TypeSymbolInfo>(semanticInfo.Type);
        }
        public TypeSymbolInfo GetType(SyntaxNode node) {
            var result = TryGetType(node);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public TypeSymbolInfo TryGetType(TypeName assemblyQualifiedName) {
            if (assemblyQualifiedName == null)
                return null;

            // resolve assembly
            var assemblyName = new AssemblyName(assemblyQualifiedName.AssemblyFullName);
            var assembly = TryGetAssembly(assemblyName);
            if (assembly == null)
                return null;

            // resolve type
            return assembly.TryGetType(assemblyQualifiedName.MakeAssembly(null));
        }
        public TypeSymbolInfo GetType(TypeName assemblyQualifiedName) {
            var result = TryGetType(assemblyQualifiedName);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public TypeSymbolInfo TryGetType(string assemblyQualifiedName) {
            int error;
            var typeName = TypeName.TryParse(assemblyQualifiedName, out error);
            if (typeName == null)
                return null;

            return TryGetType(typeName);
        }
        public TypeSymbolInfo GetType(string assemblyQualifiedName) {
            var result = TryGetType(assemblyQualifiedName);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public AssemblySymbolInfo TryGetAssembly(AssemblyName assemblyName) {
            if (m_nameToReferences == null)
                m_nameToReferences = m_pathToReferences
                    .ToDictionary(
                        o => AssemblyName.GetAssemblyName(o.Key), 
                        o => o.Value, 
                        AssemblyNameComparer.Value
                    );

            var reference = m_nameToReferences.GetValueOrDefault(assemblyName);
            if (assemblyName.Name.CaseInsensitveEquals("mscorlib"))
                reference = m_mscorlib;

            if (reference == null)
                return null;

            var assembly = Get(reference.MetadataReference);
            return Get<AssemblySymbolInfo>(assembly);
        }
        public AssemblySymbolInfo GetAssembly(AssemblyName assemblyName) {
            var result = TryGetAssembly(assemblyName);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public AssemblySymbolInfo TryGetAssembly(string file) {
            var reference = m_pathToReferences.GetValueOrDefault(file); 
            if (reference == null)
                return null;

            var assembly = Get(reference.MetadataReference);
            return Get<AssemblySymbolInfo>(assembly);
        }
        public AssemblySymbolInfo GetAssembly(string file) {
            var result = TryGetAssembly(file);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public IEnumerable<AssemblySymbolInfo> Assemblies() {
            var assemblies = m_references.Select(o => Get<AssemblySymbolInfo>(Get(o.MetadataReference)));
            if (Assembly != null)
                assemblies = assemblies.Concat(Assembly);
            return assemblies;
        }
        public IEnumerable<TypeSymbolInfo> Types() {
            return Assemblies().SelectMany(o => o.Types());
        }

        public AssemblySymbolInfo Assembly {
            get { return Get<AssemblySymbolInfo>(m_compilation.Assembly); }
        }
    }

    public sealed class MetadataReferenceInfo {

        public static MetadataReferenceInfo Create(string file, string alias = null) {
            return new MetadataReferenceInfo(file, alias);
        }

        private string m_alias;
        private MetadataReference m_reference;
        private string m_file;

        private MetadataReferenceInfo(string file, string alias) {
            m_alias = alias;
            m_file = file;

            m_reference = MetadataReference.Create(file, alias);
        }

        internal MetadataReference MetadataReference {
            get { return m_reference; }
        }

        public string Alias {
            get { return m_alias; }
        }
        public string File {
            get { return m_file; }
        }

        public override string ToString() {
            return m_file;
        }
        public override int GetHashCode() {
            return m_file.GetHashCode();
        }
        public override bool Equals(object obj) {
            if (object.ReferenceEquals(this, obj))
                return true;

            var info = obj as MetadataReferenceInfo;
            if (info == null)
                return false;

            return m_file.Equals(info.m_file);
        }
    }

    public sealed class AssemblySymbolInfo : SymbolInfo {

        private static NamespaceOrTypeSymbol TryGetNamespaceOrType(
            NamespaceOrTypeSymbol symbol, string fullname, int? arity) {
            
            return TryGetNamespaceOrType(symbol, fullname.StringSplit('.').ToStack(), arity);
        }
        private static NamespaceOrTypeSymbol TryGetNamespaceOrType(
            NamespaceOrTypeSymbol symbol, Stack<string> fullname, int? arity) {

            var name = fullname.Pop();

            // resolve namespaces
            if (fullname.Count > 0) {
                symbol = TryGetNamespaceOrType(symbol, fullname, null);
                if (symbol == null)
                    return null;
            }

            // filter on name
            var symbols = symbol.GetMembers(name);

            // filter on arity
            if (arity != null)
                symbol = symbols.OfType<NamedTypeSymbol>().SingleOrDefault(o => o.Arity == arity);
            else
                symbol = (NamespaceOrTypeSymbol)symbols.SingleOrDefault();

            if (symbol == null)
                return null;

            return symbol;
        }

        internal AssemblySymbolInfo(CompilationInfo compilation, AssemblySymbol assemblySymbol)
            : base(compilation, assemblySymbol) {
        }

        internal AssemblySymbol AssemblySymbol {
            get { return (AssemblySymbol)Symbol; }
        }

        public AssemblyName AssemblyName {
            get { return AssemblySymbol.AssemblyName; }
        }
        public string Name {
            get { return AssemblyName.Name; }
        }
        public string FullName {
            get { return AssemblyName.FullName; }
        }

        public NamespaceSymbolInfo GlobalNamespace {
            get { return Compilation.Get<NamespaceSymbolInfo>(AssemblySymbol.GlobalNamespace); }
        }
        public ModuleSymbolInfo ManifestModule {
            get { return Modules().First(); }
        }

        public TypeSymbolInfo TryGetType(TypeName fullName) {
            if (fullName == null)
                return null;

            // fully qualified?
            if (fullName.HasAssembly)
                return Compilation.TryGetType(fullName);

            // resolve element type
            if (fullName.HasElementType) {
                var elementType = TryGetType(fullName.ElementType);

                if (fullName.IsPointer)
                    return elementType.MakePointerType();

                if (fullName.IsSzArray)
                    return elementType.MakeArrayType();

                if (fullName.IsArray)
                    return elementType.MakeArrayType(fullName.ArrayRank);

                throw new NotImplementedException();
            }

            int arity;

            // try get named namespace (or global namespace) from assembly
            var typeNames = fullName.ToLinkList(o => o.DeclaringType).ToStack();
            var typeName = typeNames.Pop();
            var ns = TryGetNamespace(typeName.Namespace);
            if (ns == null)
                return null;

            // try get type from namespace
            var name = typeName.GetCSharpName(out arity);
            var type = ns.TryGetType(name, arity);
            if (type == null)
                return null;

            // try get nested types
            while ((typeName = typeNames.PopOrDefault()) != null) {

                // resolve symbol
                name = typeName.GetCSharpName(out arity);
                type = type.GetNestedType(name, arity: arity);
                if (type == null)
                    return null;
            }

            // generic arguments
            if (fullName.HasGenericArguments)
                type = type.MakeGenericType(fullName.GenericArguments().Select(o => GetType(o)));

            // return result
            return type;
        }
        public TypeSymbolInfo GetType(TypeName fullName) {
            var result = TryGetType(fullName);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public TypeSymbolInfo TryGetType(string fullname) {
            if (string.IsNullOrEmpty(fullname))
                return null;

            int error;
            var typeName = TypeName.TryParse(fullname, out error);
            if (typeName == null)
                return null;

            if (typeName.AssemblyFullName != null &&
                !typeName.AssemblyFullName.CaseInsensitveEquals(FullName))
                return null;

            return TryGetType(typeName);
        }
        public TypeSymbolInfo GetType(string fullname) {
            var typeInfo = TryGetType(fullname);
            if (typeInfo == null)
                throw new ArgumentException();
            return typeInfo;
        }

        public NamespaceSymbolInfo TryGetNamespace(string ns) {
            if (string.IsNullOrEmpty(ns))
                return GlobalNamespace;

            var nsSymbol = TryGetNamespaceOrType(GlobalNamespace.NamespaceSymbol, ns, null) as NamespaceSymbol;

            return Compilation.Get<NamespaceSymbolInfo>(nsSymbol);
        }
        public NamespaceSymbolInfo GetNamespace(string ns) {
            var nsInfo = TryGetNamespace(ns);
            if (nsInfo == null)
                throw new ArgumentException();
            return nsInfo;
        }

        public IEnumerable<NamespaceSymbolInfo> Namespaces() {
            return Node.Create(GlobalNamespace).SelectDescendentsAndSelf(o => o.Namespaces());
        }
        public IEnumerable<TypeSymbolInfo> Types() {
            return Namespaces().SelectMany(o => o.Types());
        }
        public IEnumerable<ModuleSymbolInfo> Modules() {
            return AssemblySymbol.Modules.Select(o => Compilation.Get<ModuleSymbolInfo>(o));
        }

        #region Attributes
        public new IEnumerable<AttributeSymbolInfo> Attributes(TypeSymbolInfo type = null) {
            return base.Attribute(type);
        }
        public new AttributeSymbolInfo GetAttribute(TypeSymbolInfo type = null) {
            return base.GetAttribute(type);
        }
        public new IEnumerable<dynamic> SyntheticAttributes(TypeSymbolInfo type = null) {
            return base.SyntheticAttributes(type);
        }
        public new dynamic GetSyntheticAttribute(TypeSymbolInfo type) {
            return base.GetSyntheticAttribute(type);
        }
        public new bool IsDefined(TypeSymbolInfo type) {
            return base.IsDefined(type);
        }
        #endregion

        public override SymbolInfo DeclaringSymbol {
            get { return null; } 
        }

        public override string ToString() {
            return FullName;
        }
    }

    public sealed class ModuleSymbolInfo : SymbolInfo {

        internal ModuleSymbolInfo(CompilationInfo compilation, ModuleSymbol moduleSymbol)
            : base(compilation, moduleSymbol) {
        }

        internal ModuleSymbol ModuleSymbol {
            get { return (ModuleSymbol)Symbol; }
        }

        public AssemblySymbolInfo Assembly {
            get { return Compilation.Get<AssemblySymbolInfo>(ModuleSymbol.ContainingAssembly); }
        }
        public override SymbolInfo DeclaringSymbol {
            get { return Assembly; }
        }

        #region Attributes
        public new IEnumerable<AttributeSymbolInfo> Attributes(TypeSymbolInfo type = null) {
            return base.Attribute(type);
        }
        public new AttributeSymbolInfo GetAttribute(TypeSymbolInfo type = null) {
            return base.GetAttribute(type);
        }
        public new IEnumerable<dynamic> SyntheticAttributes(TypeSymbolInfo type = null) {
            return base.SyntheticAttributes(type);
        }
        public new dynamic GetSyntheticAttribute(TypeSymbolInfo type) {
            return base.GetSyntheticAttribute(type);
        }
        public new bool IsDefined(TypeSymbolInfo type) {
            return base.IsDefined(type);
        }
        #endregion

        public override string ToString() {
            return ModuleSymbol.Name;
        }
    }

    public sealed class NamespaceSymbolInfo : SymbolInfo {

        internal NamespaceSymbolInfo(CompilationInfo compilation, NamespaceSymbol namespaceSymbol)
            : base(compilation, namespaceSymbol) {
        }

        internal NamespaceSymbol NamespaceSymbol {
            get { return (NamespaceSymbol)Symbol; }
        }

        public NamespaceSymbolInfo DeclaringNamespace {
            get { return Compilation.Get<NamespaceSymbolInfo>(NamespaceSymbol.ContainingNamespace); }
        }
        public override SymbolInfo DeclaringSymbol {
            get { return DeclaringNamespace; }
        }
        public string Name {
            get {
                if (string.IsNullOrEmpty(NamespaceSymbol.Name))
                    return null;

                return NamespaceSymbol.Name; 
            }
        }
        public string Fullname {
            get {
                var result = Name;

                var declaringNamespace = DeclaringNamespace;
                if (declaringNamespace != null && !declaringNamespace.IsGlobalNamespace)
                    result = declaringNamespace.Fullname + "." + Name;

                return result;
            }
        }

        public bool IsGlobalNamespace {
            get { return NamespaceSymbol.IsGlobalNamespace; }
        }

        public TypeSymbolInfo TryGetType(string name, int arity = 0) {
            var type = NamespaceSymbol.GetTypeMembers(name, arity).SingleOrDefault();
            return Compilation.Get<TypeSymbolInfo>(type);
        }
        public TypeSymbolInfo GetType(string name, int arity = 0) {
            var type = TryGetType(name);
            if (type == null)
                throw new ArgumentException();
            return type;
        }

        public IEnumerable<NamespaceSymbolInfo> Namespaces() {
            return NamespaceSymbol.GetNamespaceMembers()
                .Select(o => Compilation.Get<NamespaceSymbolInfo>(o));
        }
        public IEnumerable<TypeSymbolInfo> Types() {
            return NamespaceSymbol.GetTypeMembers()
                .Select(o => Compilation.Get<TypeSymbolInfo>(o));
        }

        public override string ToString() {
            return Name;
        }
    }

    public abstract class MemberSymbolInfo : SymbolInfo {

        public MemberSymbolInfo(CompilationInfo compilation, Symbol memberSymbol)
            : base(compilation, memberSymbol) {
        }

        internal virtual Accessibility Accessibility {
            get { return Symbol.DeclaredAccessibility; }
        }

        public bool IsPublic {
            get { return Accessibility == Accessibility.Public; }
        }
        public bool IsPrivate {
            get { return Accessibility == Accessibility.Private; }
        }
        public bool IsInternal {
            get { return Accessibility == Accessibility.Internal; }
        }
        public bool IsProtected {
            get { return Accessibility == Accessibility.Protected; }
        }
        public bool IsProtectedAndInternal {
            get { return Accessibility == Accessibility.ProtectedAndInternal; }
        }
        public bool IsProtectedInternal {
            get { return Accessibility == Accessibility.ProtectedInternal; }
        }

        public virtual string Name {
            get { return Symbol.Name; }
        }
        public TypeSymbolInfo DeclaringType {
            get { return Compilation.Get<TypeSymbolInfo>(Symbol.ContainingType); }
        }
        public override SymbolInfo DeclaringSymbol {
            get { return DeclaringType; }
        }
        public virtual NamespaceSymbolInfo DeclaringNamespace {
            get { return Compilation.Get<NamespaceSymbolInfo>(Symbol.ContainingNamespace); }
        }
        public AssemblySymbolInfo Assembly {
            get { return Compilation.Get<AssemblySymbolInfo>(Symbol.ContainingAssembly); }
        }

        #region Attributes
        public new IEnumerable<AttributeSymbolInfo> Attributes(TypeSymbolInfo type = null, bool declaredOnly = false) {
            return base.Attribute(type, declaredOnly);
        }
        public new AttributeSymbolInfo GetAttributes(TypeSymbolInfo type = null, bool declaredOnly = false) {
            return base.GetAttribute(type, declaredOnly);
        }
        public new IEnumerable<dynamic> SyntheticAttributes(TypeSymbolInfo type = null, bool declaredOnly = false) {
            return base.SyntheticAttributes(type, declaredOnly);
        }
        public new dynamic GetSyntheticAttribute(TypeSymbolInfo type, bool declaredOnly = false) {
            return base.GetSyntheticAttribute(type, declaredOnly);
        }
        public new bool IsDefined(TypeSymbolInfo type, bool declaredOnly = false) {
            return base.IsDefined(type, declaredOnly);
        }
        #endregion

        public override string ToString() {
            return DeclaringType.ToString() + "." + Symbol.Name;
        }
    }

    public sealed class TypeSymbolInfo : MemberSymbolInfo {

        public static bool operator==(TypeSymbolInfo lhs, Type rhs) {
            if (rhs == null || lhs == null)
                return object.Equals(lhs, rhs);

            var compilation = lhs.Compilation;
            if (compilation == null)
                throw new InvalidOperationException();
            return lhs == compilation.GetType(rhs);
        }
        public static bool operator==(Type lhs, TypeSymbolInfo rhs) {
            return rhs == lhs;
        }
        public static bool operator!=(TypeSymbolInfo lhs, Type rhs) {
            return !(lhs == rhs);
        }
        public static bool operator!=(Type lhs, TypeSymbolInfo rhs) {
            return !(lhs == rhs);
        }

        private Dictionary<Symbol, MemberSymbolInfo> m_members;

        public TypeSymbolInfo(CompilationInfo compilation, TypeSymbol typeSymbol)
            : base(compilation, typeSymbol) {
        }

        #region Private Members
        private Type GetInfoType(Type symbolType) {

            if (typeof(FieldSymbol).IsAssignableFrom(symbolType))
                return typeof(FieldSymbolInfo);

            if (typeof(MethodSymbol).IsAssignableFrom(symbolType))
                return typeof(MethodSymbolInfo);

            if (typeof(PropertySymbol).IsAssignableFrom(symbolType))
                return typeof(PropertySymbolInfo);

            if (typeof(TypeSymbol).IsAssignableFrom(symbolType))
                return typeof(TypeSymbolInfo);
                
            throw new NotSupportedException();
        }
        private TypeName GetTypeName(bool strongNamed = false, bool genericArguments = false) {
            
            TypeName typeName = null;

            if (ElementType != null) {
                // element type; add mangling after elementType's name
                var elementType = ElementType.GetTypeName(strongNamed, genericArguments);

                // pointer type
                if (IsPointer)
                    typeName = elementType.MakePointer();

                // array type
                else if (ArrayRank == 1)
                    typeName = elementType.MakeArray();

                // szArray type
                else
                    typeName = elementType.MakeArray(ArrayRank);

            } else {
                var metadataName = MetadataName;

                if (DeclaringType == null) {
                    // root type; add namespace and assembly
                    var assemblyName = strongNamed ? Assembly.FullName : null;
                    typeName = TypeName.Create(metadataName, Namespace, assemblyName);

                } else {
                    // nested type; add only nested type name
                    var declaringType = DeclaringType.GetTypeName(strongNamed);
                    typeName = declaringType.MakeNestedType(metadataName);
                }

                // generic type; add generic arguments if asked to provide them
                if (genericArguments && IsGenericType && !ContainsGenericParameters)
                    typeName = typeName.MakeGenericType(GenericArguments().Select(o => o.GetTypeName(true, true)));
            }


            return typeName;
        }
        private string ElementNameDecoration {
            get {
                if (!HasElementType)
                    return null;

                var result = default(string);

                if (IsPointer)
                    return result + "*";

                if (IsArray)
                    return result + "[" + "".PadLeft(ArrayRank - 1, ',') + "]";

                return result;
            }
        }

        private T GetMember<T>(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) where T : MemberSymbolInfo {

            return Members<T>(name, types, arity,
                isPublic, isStatic,
                declaredOnly, useExactBinding).SingleOrDefault();
        }

        private IEnumerable<T> Members<T>(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) where T : MemberSymbolInfo {

            if (IsGenericParameter)
                return Enumerable.Empty<T>();

            return Members(name, types, arity,
                isPublic, isStatic,
                declaredOnly, useExactBinding).OfType<T>();
        }
        #endregion

        #region Internal Members
        internal MemberSymbolInfo Get(Symbol symbol) {
            if (symbol == null)
                return null;

            if (m_members == null)
                m_members = new Dictionary<Symbol, MemberSymbolInfo>();

            Type type = GetInfoType(symbol.GetType());

            if (type == typeof(TypeSymbolInfo))
                return Compilation.Get<TypeSymbolInfo>(symbol);

            var result = m_members.GetOrCacheValue(symbol,
                s => (MemberSymbolInfo)type.Activate(Compilation, s));

            return result;
        }

        internal override Accessibility Accessibility {
            get {
                if (IsArray)
                    return Accessibility.Public;

                if (IsGenericParameter) {
                    if (DeclaringMethod != null)
                        return DeclaringMethod.Accessibility;

                    return DeclaringType.Accessibility;
                }

                return Symbol.DeclaredAccessibility; 
            }
        }

        internal TypeSymbol TypeSymbol {
            get { return (TypeSymbol)Symbol; }
        }
        internal TypeParameterSymbol TypeParameterSymbol {
            get { return (TypeParameterSymbol)Symbol; }
        }
        internal NamedTypeSymbol NamedTypeSymbol {
            get { return (NamedTypeSymbol)Symbol; }
        }
        internal PointerTypeSymbol PointerTypeSymbol {
            get { return (PointerTypeSymbol)Symbol; }
        }
        internal ArrayTypeSymbol ArrayTypeSymbol {
            get { return (ArrayTypeSymbol)Symbol; }
        }

        internal bool TypeCheckEnum(dynamic value) {

            var type = ((object)value).GetType();
            if (type.IsPrimitive)
                return true;

            if (!type.IsEnum)
                return false;

            return Compilation.TryGetType(type) == this;
        }
        #endregion

        #region Identity
        public override SymbolInfo DeclaringSymbol {
            get { return (SymbolInfo)DeclaringMethod ?? DeclaringType; }
        }
        public MethodSymbolInfo DeclaringMethod {
            get { return Compilation.Get<MethodSymbolInfo>(TypeSymbol.ContainingSymbol as MethodSymbol); }
        }
        public TypeSymbolInfo BaseType {
            get {
                if (IsGenericParameter) {
                    if (HasStructConstraint)
                        return Compilation.GetType(typeof(ValueType));

                    var classConstraint = GenericParameterConstraints().FirstOrDefault(o => o.IsClass);
                    if (classConstraint != null)
                        return classConstraint;

                    return Compilation.GetType(typeof(object));
                }

                return Compilation.Get<TypeSymbolInfo>(TypeSymbol.BaseType); 
            }
        }
        public IEnumerable<TypeSymbolInfo> Interfaces() {
            return TypeSymbol.AllInterfaces.Select(o => Compilation.Get<TypeSymbolInfo>(o));
        }
        public Type RuntimeType {
            get { throw new NotImplementedException(); }
        }
        #endregion

        #region ElementType
        public bool HasElementType {
            get { return ElementType != null; }
        }
        public TypeSymbolInfo ElementType {
            get {
                if (IsArray)
                    return Compilation.Get<TypeSymbolInfo>(ArrayTypeSymbol.ElementType);

                if (IsPointer)
                    return Compilation.Get<TypeSymbolInfo>(PointerTypeSymbol.PointedAtType);

                return null;
            }
        }
        public TypeSymbolInfo RootElementType {
            get {
                if (!HasElementType)
                    return this;

                var elementType = this;
                while (elementType.HasElementType)
                    elementType = elementType.ElementType;

                return elementType;
            }
        }
        public TypeSymbolInfo MakePointerType() {
            return Compilation.Get<TypeSymbolInfo>(Compilation.Compilation.CreatePointerTypeSymbol(TypeSymbol));
        }
        public TypeSymbolInfo MakeArrayType(int rank = 1) {
            return Compilation.Get<TypeSymbolInfo>(Compilation.Compilation.CreateArrayTypeSymbol(TypeSymbol, rank));
        }
        public int ArrayRank {
            get { 
                if (!IsArray)
                    throw new InvalidOperationException();

                return ArrayTypeSymbol.Rank; 
            }
        }
        #endregion

        #region MetadataName
        public string MetadataName {
            get {
                if (HasElementType)
                    return ElementType.MetadataName + ElementNameDecoration;

                var name = Name;

                if (IsGenericType)
                    name += "`" + GenericArity;

                return name;
            }
        }
        public string MetadataFullName {
            get {
                if (ContainsGenericParameters && !IsGenericTypeDefinition)
                    return null;

                if (IsGenericParameter)
                    return null;

                return GetTypeName(genericArguments: true).ToString();
            }
        }
        public string AssemblyQualifiedName {
            get {
                if (ContainsGenericParameters && !IsGenericTypeDefinition)
                    return null;

                if (IsGenericParameter)
                    return null;

                return GetTypeName(strongNamed: true, genericArguments: true).ToString();
            }
        }
        #endregion

        #region Name
        public string KeywordName {
            get { return Compilation.GetTypeKeyword(this); }
        }
        public override string Name {
            get {
                if (HasElementType)
                    return ElementType.Name + ElementNameDecoration;

                return Symbol.Name; 
            }
        }
        public string Namespace {
            get {
                if (HasElementType)
                    return RootElementType.Namespace;

                return DeclaringNamespace != null ? DeclaringNamespace.Fullname : null; 
            }
        }
        #endregion

        #region Modifiers
        public bool IsAbstract {
            get { return TypeSymbol.IsAbstract; }
        }
        public bool IsSealed {
            get {
                if (IsArray)
                    return true;

                return TypeSymbol.IsSealed; 
            }
        }
        public bool IsExtern {
            get { return TypeSymbol.IsExtern; }
        }
        public bool IsStatic {
            get { return TypeSymbol.IsStatic; }
        }
        #endregion

        #region Type of Type
        public bool IsValueType {
            get { return TypeSymbol.IsValueType; }
        }
        public bool IsReferenceType {
            get {
                if (IsGenericParameter && GenericParameterConstraints().Any(o => o.IsClass))
                    return true;

                return TypeSymbol.IsReferenceType; 
            }
        }

        public bool IsInterface {
            get { return TypeSymbol.TypeKind == TypeKind.Interface; }
        }
        public bool IsClass {
            get { return TypeSymbol.TypeKind == TypeKind.Class; }
        }
        public bool IsStruct {
            get { return TypeSymbol.TypeKind == TypeKind.Struct; }
        }
        public bool IsEnum {
            get { return TypeSymbol.TypeKind == TypeKind.Enum; }
        }
        public bool IsArray {
            get { return TypeSymbol.TypeKind == TypeKind.ArrayType; }
        }
        public bool IsDelegate {
            get { return TypeSymbol.TypeKind == TypeKind.Delegate; }
        }
        public bool IsPointer {
            get { return TypeSymbol.TypeKind == TypeKind.PointerType; }
        }
        #endregion

        #region Generic Type
        public bool IsNamedType {
            get { return Symbol is NamedTypeSymbol; }
        }

        public int GenericArity {
            get {
                if (!IsGenericType)
                    return 0;

                return NamedTypeSymbol.Arity; 
            }
        }
        public bool IsGenericType {
            get { return GenericArguments().Any(); }
        }
        public bool IsGenericTypeDefinition {
            get { return IsGenericType && this == GenericTypeDefinition; }
        }
        public bool ContainsGenericParameters {
            get {
                if (IsGenericTypeDefinition)
                    return true;

                return GenericArguments().Any(o => o.IsGenericParameter || o.ContainsGenericParameters);
            }
        }
        public TypeSymbolInfo GenericTypeDefinition {
            get {
                if (!IsGenericType)
                    return this;
                return Compilation.Get<TypeSymbolInfo>(Symbol.OriginalDefinition); 
            }
        }
        public IEnumerable<TypeSymbolInfo> GenericArguments() {
            if (!IsNamedType)
                return Enumerable.Empty<TypeSymbolInfo>();
            return NamedTypeSymbol.TypeArguments.Select(o => Compilation.Get<TypeSymbolInfo>(o));
        }
        public TypeSymbolInfo MakeGenericType(IEnumerable<TypeSymbolInfo> genericArguments) {
            return MakeGenericType(genericArguments.ToArray());
        }
        public TypeSymbolInfo MakeGenericType(params TypeSymbolInfo[] genericArguments) {
            if (!IsGenericTypeDefinition)
                throw new InvalidOperationException();

            var arguments = genericArguments.Select(o => o.TypeSymbol);

            return Compilation.Get<TypeSymbolInfo>(NamedTypeSymbol.Construct(arguments.ToArray()));
        }
        #endregion

        #region Generic Parameter
        public bool IsGenericParameter {
            get { return Symbol is TypeParameterSymbol; }
        }
        public int GenericParameterPosition {
            get {
                if (!IsGenericParameter)
                    throw new InvalidOperationException();

                return TypeParameterSymbol.DeclaringType.TypeParameters.IndexOf(TypeParameterSymbol); 
            }
        }
        public IEnumerable<TypeSymbolInfo> GenericParameterConstraints() {
            if (!IsGenericParameter)
                return Enumerable.Empty<TypeSymbolInfo>();

            return TypeParameterSymbol.ConstraintTypes.Select(o => Compilation.Get<TypeSymbolInfo>(o));
        }
        public bool HasClassConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.HasReferenceTypeConstraint;
            }
        }
        public bool HasStructConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.HasValueTypeConstraint;
            }
        }
        public bool HasNewConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.HasConstructorConstraint;
            }
        }
        public bool HasOutConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.Variance == VarianceKind.Out;
            }
        }
        public bool HasInConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.Variance == VarianceKind.In;
            }
        }
        #endregion

        #region Members
        public FieldSymbolInfo GetField(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return GetMember<FieldSymbolInfo>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public IEnumerable<FieldSymbolInfo> Fields(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return Members<FieldSymbolInfo>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public PropertySymbolInfo GetProperty(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return GetMember<PropertySymbolInfo>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public IEnumerable<PropertySymbolInfo> Properties(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return Members<PropertySymbolInfo>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public TypeSymbolInfo GetNestedType(string name = null, int arity = 0, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return GetMember<TypeSymbolInfo>(name, arity: arity, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public IEnumerable<TypeSymbolInfo> NestedTypes(string name = null, int arity = 0, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return Members<TypeSymbolInfo>(name, arity: arity, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }

        public MethodSymbolInfo GetMethod(
            string name = null, object types = null, 
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            return GetMember<MethodSymbolInfo>(name, types: types, 
                isPublic: isPublic, isStatic: isStatic, 
                declaredOnly: declaredOnly, useExactBinding: useExactBinding);
        }
        public IEnumerable<MethodSymbolInfo> Methods(
            string name = null, object types = null,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            return Members<MethodSymbolInfo>(name, types: types,
                isPublic: isPublic, isStatic: isStatic,
                declaredOnly: declaredOnly, useExactBinding: useExactBinding);
        }

        public MemberSymbolInfo GetMember(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            return Members(name, types, arity,
                isPublic, isStatic,
                declaredOnly, useExactBinding).Single();
        }

        public IEnumerable<MemberSymbolInfo> Members(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            if (IsGenericParameter)
                return Enumerable.Empty<MemberSymbolInfo>();

            var members =
                from o in name == null ? NamedTypeSymbol.GetMembers() : NamedTypeSymbol.GetMembers(name)
                where name == null || o.Name == name
                let type = o as NamedTypeSymbol where type == null || type.Arity == arity
                let method = o as MethodSymbol where method == null || method.Arity == arity
                where isStatic == null || isStatic == o.IsStatic
                where isPublic == null || (o.DeclaredAccessibility == Accessibility.Public) == isPublic
                select o;

            return members.Select(o => Get(o));
        }
        #endregion

        #region Enum
        private bool? m_enumContainsFlagsCache;

        public bool EnumContainsFlags {
            get { 
                if (!IsEnum) 
                    return false;

                if (m_enumContainsFlagsCache == null)
                    m_enumContainsFlagsCache = IsDefined(Compilation.GetType(typeof(FlagsAttribute)));

                return (bool)m_enumContainsFlagsCache;
            }
        }
        public IEnumerable<FieldSymbolInfo> EnumFields() {
            if (!IsEnum)
                return Enumerable.Empty<FieldSymbolInfo>();

            var fields =
                from o in Fields()
                orderby o.IsEnumUberFlag, (long)o.RawConstantValue
                select o;

            return fields;
        }
        public IEnumerable<FieldSymbolInfo> EnumValues(dynamic value = null, bool minimumSet = true) {
            if (value == null)
                minimumSet = false;

            if (!IsEnum)
                return Enumerable.Empty<FieldSymbolInfo>();

            var result = EnumFields().Where(o => !o.IsEnumMask);

            if (value != null)
                result = result.Where(o => o.TestEnumValue(value));

            if (minimumSet) {
                var set = result.ToArray();
                result = result.Where(o => set.All(x => o == x || !o.TestEnumValue(x)));
            }

            return result;
        }
        public IEnumerable<FieldSymbolInfo> EnumMasks() {
            if (!IsEnum)
                return Enumerable.Empty<FieldSymbolInfo>();

            return EnumFields().Where(o => o.IsEnumMask);
        }

        public FieldSymbolInfo GetEnumValue(dynamic value) {
            if (!TypeCheckEnum(value))
                return null;

            var lValue = (long)value;
            return EnumValues().SingleOrDefault(o => (long)o.RawConstantValue == lValue);
        }
        public FieldSymbolInfo GetEnumMask(dynamic value) {
            if (!TypeCheckEnum(value))
                return null;

            var lValue = (long)value;
            return EnumMasks().SingleOrDefault(o => (long)o.RawConstantValue == lValue);
        }
        #endregion

        public override string ToString() {
            var result = Name;

            if (IsGenericParameter)
                return result;

            if (DeclaringType != null)
                result = DeclaringType.ToString() + "." + Name;

            else if (Namespace != null)
                result = Namespace + "." + result;

            if (IsGenericType)
                result = result + GenericArguments().StringJoin("<", ",", o => o.ToString(), ">");

            return result;
        }
    }

    public sealed class MethodSymbolInfo : MemberSymbolInfo {

        private Dictionary<Symbol, ParameterSymbolInfo> m_parameters;

        internal MethodSymbolInfo(CompilationInfo compilation, MethodSymbol methodSymbol)
            : base(compilation, methodSymbol) {
        }

        private ParameterSymbolInfo Get(Symbol symbol) {
            if (symbol == null)
                return null;

            if (m_parameters == null)
                m_parameters = new Dictionary<Symbol, ParameterSymbolInfo>();

            var result = m_parameters.GetOrCacheValue(symbol,
                s => (ParameterSymbolInfo)typeof(ParameterSymbolInfo).Activate(Compilation, s));

            return result;
        }
        private MethodSymbol MethodSymbol {
            get { return (MethodSymbol)Symbol; }
        }

        #region Kind
        public bool IsConstructor {
            get { return MethodSymbol.MethodKind == MethodKind.Constructor || IsStaticConstructor; }
        }
        public bool IsInstanceConstructor {
            get { return IsConstructor && !IsStatic; }
        }
        public bool IsStaticConstructor {
            get { return MethodSymbol.MethodKind == MethodKind.StaticConstructor; }
        }
        public bool IsFinalizer {
            get { return MethodSymbol.MethodKind == MethodKind.Destructor; }
        }

        public bool IsPropertyGet {
            get { return MethodSymbol.MethodKind == MethodKind.PropertyGet; }
        }
        public bool IsPropertySet {
            get { return MethodSymbol.MethodKind == MethodKind.PropertySet; }
        }
        public bool IsEventAdd {
            get { return MethodSymbol.MethodKind == MethodKind.EventAdd; }
        }
        public bool IsEventRemove {
            get { return MethodSymbol.MethodKind == MethodKind.EventRemove; }
        }

        public bool IsAnonymousFunction {
            get { return MethodSymbol.MethodKind == MethodKind.AnonymousFunction; }
        }
        public bool IsExplicitInterfaceImplementation {
            get { return MethodSymbol.MethodKind == MethodKind.ExplicitInterfaceImplementation; }
        }
        public bool IsDelegateInvoke {
            get { return MethodSymbol.MethodKind == MethodKind.DelegateInvoke; }
        }

        public bool IsOperator {
            get { return MethodSymbol.MethodKind == MethodKind.Operator; }
        }
        public bool IsConversion {
            get { return MethodSymbol.MethodKind == MethodKind.Conversion; }
        }
        #endregion

        #region Modifiers
        public bool IsVirtual {
            get { return MethodSymbol.IsVirtual; }
        }
        public bool IsAbstract {
            get { return MethodSymbol.IsAbstract; }
        }
        public bool IsOverride {
            get { return MethodSymbol.IsOverride; }
        }
        public bool IsSealed {
            get { return MethodSymbol.IsSealed; }
        }
        public bool IsExtern {
            get { return MethodSymbol.IsExtern; }
        }
        public bool IsStatic {
            get { return MethodSymbol.IsStatic; }
        }
        #endregion

        public IEnumerable<ParameterSymbolInfo> Parameters() {
            return MethodSymbol.Parameters.Select(o => Get(o));
        }

        #region Generic
        public bool IsGenericMethod {
            get { return MethodSymbol.IsGenericMethod; }
        }
        public bool IsGenericMethodDefinition {
            get {
                return MethodSymbol.TypeParameters.ToEnumerable().Zip(
                    MethodSymbol.TypeArguments.ToEnumerable(), (l, r) => l.Equals(r))
                    .All(o => true);
            }
        }
        public bool HasGenericArguments {
            get { return GenericArguments().Any(); }
        }
        public MethodSymbolInfo GenericMethodDefinition {
            get { return (MethodSymbolInfo)DeclaringType.Get(MethodSymbol.ConstructedFrom); }
        }
        public IEnumerable<TypeSymbolInfo> GenericArguments() {
            return MethodSymbol.TypeArguments.Select(o => Compilation.Get<TypeSymbolInfo>(o));
        }
        public MethodSymbolInfo MakeGenericMethod(IEnumerable<TypeSymbolInfo> genericArguments) {
            return MakeGenericMethod(genericArguments.ToArray());
        }
        public MethodSymbolInfo MakeGenericMethod(params TypeSymbolInfo[] genericArguments) {
            var arguments = genericArguments.Select(o => o.TypeSymbol);
            var readOnlyArguments = ReadOnlyArray<TypeSymbol>.CreateFrom(arguments.ToArray());
            return Compilation.Get<MethodSymbolInfo>(MethodSymbol.Construct(readOnlyArguments));
        }
        #endregion
    }

    public sealed class ParameterSymbolInfo : SymbolInfo {

        internal ParameterSymbolInfo(CompilationInfo compilation, ParameterSymbol parameterSymbol)
            : base(compilation, parameterSymbol) {
        }

        private ParameterSymbol ParameterSymbol {
            get { return (ParameterSymbol)Symbol; }
        }

        public string Name {
            get { return ParameterSymbol.Name; }
        }
        public int Position {
            get { return ParameterSymbol.Ordinal; }
        }
        public TypeSymbolInfo Type {
            get { return Compilation.Get<TypeSymbolInfo>(ParameterSymbol.Type); }
        }

        public TypeSymbolInfo DeclaringType {
            get { return Compilation.Get<TypeSymbolInfo>(ParameterSymbol.ContainingType); }
        }
        public MethodSymbolInfo DeclaringMethod {
            get { return DeclaringType.Get(ParameterSymbol.ContainingSymbol) as MethodSymbolInfo; }
        }
        public PropertySymbolInfo DeclaringProperty {
            get { return DeclaringType.Get(ParameterSymbol.ContainingSymbol) as PropertySymbolInfo; }
        }
        public override SymbolInfo DeclaringSymbol {
            get { return (SymbolInfo)DeclaringProperty ?? (SymbolInfo)DeclaringMethod ?? DeclaringType; }
        }

        public dynamic DefaultValue { 
            get { 
                var result = ParameterSymbol.DefaultValue;
                if (result == null)
                    return result;

                if (Type.IsEnum)
                    result = SyntheticEnum.Construct(result, Type);

                return result;
            } 
        }
        public bool HasDefaultValue { 
            get { return ParameterSymbol.HasDefaultValue; } 
        }

        public bool IsExtern { 
            get { return ParameterSymbol.IsExtern; } 
        }
        public bool IsOptional { 
            get { return ParameterSymbol.IsOptional; } 
        }
        public bool IsParams { 
            get { return ParameterSymbol.IsParams; } 
        }
        public bool IsThis {
            get { return ParameterSymbol.IsThis; }
        }
        public bool IsOut {
            get { return ParameterSymbol.RefKind == RefKind.Out; }
        }
        public bool IsRef {
            get { return ParameterSymbol.RefKind == RefKind.Ref; }
        }

        public override string ToString() {
            return ParameterSymbol.Name;
        }
    }

    public sealed class FieldSymbolInfo : MemberSymbolInfo {

        internal FieldSymbolInfo(CompilationInfo compilation, FieldSymbol fieldSymbol)
            : base(compilation, fieldSymbol) {
        }

        private FieldSymbol FieldSymbol {
            get { return (FieldSymbol)Symbol; }
        }

        internal dynamic RawConstantValue {
            get { return FieldSymbol.ConstantValue; }
        }

        public TypeSymbolInfo Type {
            get { return Compilation.Get<TypeSymbolInfo>(FieldSymbol.Type); }
        }

        public dynamic ConstantValue {
            get {
                var result = RawConstantValue;
                if (result == null)
                    return result;

                if (Type.IsEnum)
                    result = SyntheticEnum.Construct(result, Type);

                return result;
            } 
        }
        public bool HasConstantValue { 
            get { return FieldSymbol.HasConstantValue; } 
        }
        public bool IsConst { 
            get { return FieldSymbol.IsConst; } 
        }
        public bool IsExtern { 
            get { return FieldSymbol.IsExtern; } 
        }
        public bool IsReadOnly { 
            get { return FieldSymbol.IsReadOnly; } 
        }
        public bool IsVolatile { 
            get { return FieldSymbol.IsVolatile; } 
        }

        #region Enum
        private FieldSymbolInfo m_enumMask;
        private bool m_isEnumMaskCached;

        public bool IsEnumMask {
            get {
                if (!Type.EnumContainsFlags)
                    return false;

                return Name.EndsWith("Mask");
            }
        }
        public bool IsEnumFlag {
            get { return Type.EnumContainsFlags && !IsEnumMask && !HasEnumMask && !IsEnumDefaultValue; }
        }
        public bool IsEnumUberFlag {
            get { return Type.EnumContainsFlags && RawConstantValue == -1; } 
        }
        public bool IsEnumDefaultValue { 
            get { return Type.IsEnum && RawConstantValue == 0; } 
        }

        public bool HasEnumMask {
            get { return EnumMask != null; }
        }
        public FieldSymbolInfo EnumMask {
            get {
                if (!Type.EnumContainsFlags || IsEnumMask || IsEnumDefaultValue)
                    return null;

                if (!m_isEnumMaskCached) {
                    m_enumMask = Type.EnumMasks().SingleOrDefault(mask =>
                        ((long)RawConstantValue & (long)mask.RawConstantValue) == (long)RawConstantValue);
                    m_isEnumMaskCached = true;
                }

                return m_enumMask;
            }
        }
        public IEnumerable<FieldSymbolInfo> EnumMaskValues() {
            if (!IsEnumMask)
                return Enumerable.Empty<FieldSymbolInfo>();

            return Type.EnumValues().Where(value => !value.IsEnumDefaultValue &&
                ((long)RawConstantValue & (long)value.RawConstantValue) == (long)value.RawConstantValue);
        }
        public IEnumerable<FieldSymbolInfo> AggregatingEnumFlags() {
            if (!IsEnumFlag)
                return Enumerable.Empty<FieldSymbolInfo>();

            return Type.EnumValues().Where(o => o.AggregatedEnumFlags().Any(x => x == this));
        }
        public IEnumerable<FieldSymbolInfo> AggregatedEnumFlags() {
            if (!IsEnumFlag)
                return Enumerable.Empty<FieldSymbolInfo>();

            return Type.EnumValues().Except(new[] { this })
                .Where(o => !o.IsEnumDefaultValue && o.IsEnumFlag)
                .Where(o => ((long)RawConstantValue & (long)o.RawConstantValue) == (long)o.RawConstantValue);
        }

        public bool TestEnumValue(dynamic value) {

            // allow FieldSymbolInfo to be passed in as a value
            var asField = value as FieldSymbolInfo;
            if (asField != null) {
                if (asField.Type != Type)
                    return false;
                value = asField.RawConstantValue;
            }

            // ensure the field is an enum value
            if (!Type.IsEnum)
                throw new InvalidOperationException();

            if (value == null)
                throw new ArgumentException();

            // type check the argument
            if (!Type.TypeCheckEnum(value))
                return false;

            // convert the constant value and the argument to longs
            var lConstant = (long)RawConstantValue;
            var lValue = (long)value;

            // test if both are zero
            if (lValue == 0 || lConstant == 0)
                return lValue == lConstant;

            // mask the value
            if (HasEnumMask || IsEnumMask) {
                var mask = EnumMask ?? this;
                lValue &= (long)mask.RawConstantValue;
            }

            // do any masked enum values match the masked value
            if (IsEnumMask)
                return EnumMaskValues().Any(o => (long)o.RawConstantValue == lValue);
            
            // this can be flag without a mask if the enum has FlagsAttribute set
            else if (IsEnumFlag)
                return (lValue & lConstant) == lConstant;

            // check the value
            return lValue == lConstant;
        }
        #endregion

        #region Attributes
        public IEnumerable<AttributeSymbolInfo> Attributes(TypeSymbolInfo type = null) {
            return base.Attributes(type);
        }
        public AttributeSymbolInfo GetAttribute(TypeSymbolInfo type = null) {
            return base.GetAttributes(type);
        }
        public IEnumerable<dynamic> SyntheticAttributes(TypeSymbolInfo type = null) {
            return base.SyntheticAttributes(type);
        }
        public dynamic GetSyntheticAttribute(TypeSymbolInfo type) {
            return base.GetSyntheticAttribute(type);
        }
        public bool IsDefined(TypeSymbolInfo type) {
            return base.IsDefined(type);
        }
        #endregion
    }

    public sealed class PropertySymbolInfo : MemberSymbolInfo {

        private Dictionary<Symbol, ParameterSymbolInfo> m_parameters;

        internal PropertySymbolInfo(CompilationInfo compilation, PropertySymbol propertySymbol)
            : base(compilation, propertySymbol) {
        }

        private ParameterSymbolInfo Get(Symbol symbol) {
            if (symbol == null)
                return null;

            if (m_parameters == null)
                m_parameters = new Dictionary<Symbol, ParameterSymbolInfo>();

            var result = m_parameters.GetOrCacheValue(symbol,
                s => (ParameterSymbolInfo)typeof(ParameterSymbolInfo).Activate(Compilation, s));

            return result;
        }
        private PropertySymbol PropertySymbol {
            get { return (PropertySymbol)Symbol; }
        }

        public TypeSymbolInfo Type {
            get { return Compilation.Get<TypeSymbolInfo>(PropertySymbol.Type); }
        }

        public bool IsAbstract {
            get { return PropertySymbol.IsAbstract; }
        }
        public bool IsExtern {
            get { return PropertySymbol.IsExtern; }
        }
        public bool IsOverride {
            get { return PropertySymbol.IsOverride; }
        }
        public bool IsSealed {
            get { return PropertySymbol.IsSealed; }
        }
        public bool IsStatic {
            get { return PropertySymbol.IsStatic; }
        }
        public bool IsVirtual {
            get { return PropertySymbol.IsVirtual; }
        }

        public PropertySymbolInfo OverriddenProperty { 
            get { return Compilation.Get<PropertySymbolInfo>(PropertySymbol.OverriddenProperty); } 
        }

        public bool IsReadOnly { 
            get { return PropertySymbol.IsReadOnly; } 
        }
        public bool IsWriteOnly { 
            get { return PropertySymbol.IsWriteOnly; } 
        }

        public MethodSymbolInfo GetMethod {
            get { return (MethodSymbolInfo)DeclaringType.Get(PropertySymbol.GetMethod); } 
        }
        public MethodSymbolInfo SetMethod {
            get { return (MethodSymbolInfo)DeclaringType.Get(PropertySymbol.SetMethod); } 
        }

        public bool IsIndexer { 
            get { return PropertySymbol.IsIndexer; } 
        }
        public IEnumerable<ParameterSymbolInfo> Parameters { 
            get { return PropertySymbol.Parameters.Select(o => Get(o)); } 
        }
    }

    public sealed class AttributeSymbolInfo : SymbolInfo {

        private CompilationInfo m_compilation;
        private AttributeData m_data;
        private SymbolInfo m_symbol;
        private SyntheticAttribute m_value;

        internal AttributeSymbolInfo(CompilationInfo compilation, SymbolInfo symbol, AttributeData data)
            : base(compilation, null) {
            m_data = data;
            m_symbol = symbol;
            m_compilation = compilation;
        }

        public override SymbolInfo DeclaringSymbol {
            get { return m_symbol; }
        }
        public TypeSymbolInfo Type { 
            get { return m_compilation.Get<TypeSymbolInfo>(m_data.AttributeClass); } 
        }
        public MethodSymbolInfo Constructor { 
            get { return m_compilation.Get<MethodSymbolInfo>(m_data.AttributeConstructor); } 
        }
        public IEnumerable<KeyValuePair<MemberSymbolInfo, TypedConstantInfo>> NamedArguments() {
            return m_data.NamedArguments.Select(o =>
                new KeyValuePair<MemberSymbolInfo, TypedConstantInfo>(
                    Type.GetMember(o.Key),
                    new TypedConstantInfo(m_compilation, o.Value)
                )
            );
        }
        public IEnumerable<TypedConstantInfo> Arguments() {
            return m_data.PositionalArguments.Select(o => new TypedConstantInfo(m_compilation, o));
        }
        public dynamic Value {
            get {
                if (m_value == null)
                    m_value = new SyntheticAttribute(this);
                return m_value;
            }
        }
    }

    public struct TypedConstantInfo {

        private CompilationInfo m_compilation;
        private TypedConstant m_value;

        internal TypedConstantInfo(CompilationInfo compilation, TypedConstant value) {
            m_compilation = compilation;
            m_value = value;
        }

        public CompilationInfo Compilation {
            get { return m_compilation; }
        }

        public bool IsString {
            get { return Type == Compilation.GetType(typeof(string)); }
        }
        public bool IsCharacter {
            get { return Type == Compilation.GetType(typeof(char)); }
        }
        public bool IsPrimitive {
            get { return m_value.Kind == TypedConstantKind.Primitive && !IsString; }
        }
        public bool IsEnum {
            get { return m_value.Kind == TypedConstantKind.Enum; }
        }
        public bool IsType {
            get { return m_value.Kind == TypedConstantKind.Type; }
        }
        public bool IsArray {
            get { return m_value.Kind == TypedConstantKind.Array; }
        }

        public TypeSymbolInfo Type {
            get { return m_compilation.Get<TypeSymbolInfo>(m_value.Type); }
        }
        public object Value {
            get {
                if (IsArray)
                    return Values;

                var typeSymbol = m_value.Value as NamedTypeSymbol;

                if (typeSymbol != null)
                    return Compilation.Get<TypeSymbolInfo>(typeSymbol);

                return m_value.Value; 
            }
        }
        public IEnumerable<TypedConstantInfo> Values {
            get {
                if (!IsArray)
                    throw new InvalidOperationException();

                var compilation = m_compilation;

                return m_value.Values.Select(o => new TypedConstantInfo(compilation, o));
            }
        }
        public int Length {
            get {
                if (!IsArray)
                    throw new InvalidOperationException();
                return m_value.Values.Count; 
            }
        }
    }

    internal sealed class SyntheticAttribute : DynamicObject {

        private static IEnumerable<KeyValuePair<string, TypedConstantInfo>> DynamicProperties(AttributeSymbolInfo data) {

            // named arguments
            foreach (var namedArgument in data.NamedArguments()) {
                yield return new KeyValuePair<string, TypedConstantInfo>(
                    namedArgument.Key.Name, namedArgument.Value);
            }

            // constructor arguments
            foreach (var constructorArgument in data.Constructor.Parameters()
                .Zip(data.Arguments(), (l, r) => new { Parameter = l, Argument = r })) {

                // capitalize the first character of the constructor parameter name
                var name = constructorArgument.Parameter.Name.ToUpperFirst();

                yield return new KeyValuePair<string, TypedConstantInfo>(name, constructorArgument.Argument);
            }
        }
        private static dynamic GetDynamicPropertyValue(TypedConstantInfo constant) {
            var compilation = constant.Compilation;

            // synthisize array and enum dynamic objects
            if (constant.IsArray) {

                // array type is object...
                var arrayType = typeof(object);

                // ...unless all elements are mscorlib primitives
                var elementType = constant.Type.ElementType;
                if (!elementType.IsEnum && elementType != compilation.TryGetType(typeof(object)))
                    arrayType = elementType.RuntimeType;

                // allocate the array and copy the values into it
                return constant.Values.Select(o => GetDynamicPropertyValue(o)).ToArray(arrayType);    
            }

            // synthisized enum
            if (constant.IsEnum)
                return SyntheticEnum.Construct(constant.Value, constant.Type);

            // mscorlib primitve
            return constant.Value;
        }

        private AttributeSymbolInfo m_data;
        private Dictionary<string, object> m_values;

        internal SyntheticAttribute(AttributeSymbolInfo data) {
            m_data = data;
            m_values = new Dictionary<string, object>();

            foreach (var property in DynamicProperties(m_data)) {
                var name = property.Key;
                var constant = property.Value;

                m_values[name] = GetDynamicPropertyValue(constant);
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            return m_values.TryGetValue(binder.Name, out result);
        }
    }

    internal sealed class SyntheticEnum : DynamicObject {

        internal static dynamic Construct(object e, TypeSymbolInfo type) {
            var value = (long)(dynamic)e;
            var values = type.Fields().ToDictionary(o => o.Name, o => (long)o.RawConstantValue);
            return new SyntheticEnum(value, values);
        }

        private long m_value;
        private Dictionary<string, long> m_values;

        internal SyntheticEnum(long value, Dictionary<string, long> values) {
            m_value = value;
            m_values = values;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            var name = binder.Name;
            var value = m_values.GetValueOrDefault(name);
            if (value == null)
                throw new MissingFieldException();

            result = m_value == value;
            return true;
        }
    }
}
